# -*- coding: utf-8 -*-

# Projet CellMates
# Judith ROBSON
# Roxane LEVY
# Ludovic JURADO



### OBSERVATIONS



### OBSERVATIONS



import re



class TransRegles :
	"""
	Classe permettant la traduction de la syntaxe interne des règles de transition d'un automate vers un pseudo langage plus facilement compréhensible par l'utilisateur et vice versa.

	Le constructeur prend en arguments optionnels les différents connecteurs qui structurent le pseudo langage.
	Il est ainsi possible de le personnaliser dans une certaine mesure
	(y compris pour la lisibilité à l'affichage, puisqu'espaces, sauts de ligne et tabulations sont pris en compte / l'écriture des règles est en revanche libre à ce niveau).

	Il y a deux méthodes à utiliser :
	- intToExt(regles) : prend en argument les règles de transition au format interne et retourne une chaine les représentant en pseudo langage (sans vérifications, puisque ces règles étant produite par le moteur de l'automate, elles sont justes par définition)
	- extToInt(reglesExt) : prend en argument une chaine représentant les règles en pseudo langage et les traduit au format interne (avec vérification de la syntaxe et des valeurs, puisque ces règles sont écrite par l'utilisateur, qui peut commettre des erreurs)

	Exemple avec de règles exploitant tous les connecteurs :

	Format interne :

	{
		0: {1: {1: [1, 2, 5], 2: [2, 3, 4]}, 2: {1: [2, 4], 2: [3, 5]}},
		1: {1: {1: [3, 4], 2: [3, 6]}, 2: {1: [2], 2: [0, 3, 4]}},
		2: {1: {1: [5, 6], 2: [0, 1, 5]}, 2: {1: [0, 6], 2: [5, 7]}}
	}


	Pseudo langage :

	from 0
		to 1 if (1 or 2 or 5) in 1 and (2 or 3 or 4) in 2
		to 2 if (2 or 4) in 1 and (3 or 5) in 2
	from 1
		to 1 if (3 or 4) in 1 and (3 or 6) in 2
		to 2 if (2) in 1 and (0 or 3 or 4) in 2
	from 2
		to 1 if (5 or 6) in 1 and (0 or 1 or 5) in 2
		to 2 if (0 or 6) in 1 and (5 or 7) in 2


	Du point de vue de l'utilisateur le langage est relativement flexible.
	Du moment que tous les connecteurs sont présents et s'enchaînent dans le bon ordre (et que les règles sont complètes et cohérentes, notamment vis à vis de la configuration), peu importe le nombre d'espaces ou de tabulations.
	Il est de même possible de s'affranchir des sauts de lignes.
	Ainsi, pour le jeu de la vie par exemple, cette écriture condensée sera valide :

	from0 to1 if(3) in1 from1 to1 if(2 or 3) in1

	Ou encore pour "Seeds" :

	from 0 to 1 if(2) in 1
	from 1 to 1 if() in 1
	"""

	def __init__(self,
	fromCo="from ",
	toCo="\n\tto ",
	ifCo=" if ",
	subRegJoin="()",
	orCo=" or ",
	inCo=" in ",
	andCo=" and ",
	regSep="\n\n") :
		# connecteurs
		self.fromCo = fromCo
		self.toCo = toCo
		self.ifCo = ifCo
		self.subRegJoin = subRegJoin
		self.orCo = orCo
		self.inCo = inCo
		self.andCo = andCo
		self.regSep = regSep

	# conversion de la syntaxe interne des règles vers la syntaxe utilisateur
	def intToExt(self, regles) :
		reglesExt = ''
		for etatDepart, conditions in regles.items() :
			reglesExt += self.fromCo + str(etatDepart)
			for etatCible, regle in conditions.items() :
				reglesExt += self.toCo + str(etatCible) + self.ifCo
				for etatVoisin, sousRegles in regle.items() :
					reglesExt += self.subRegJoin[0] + self.orCo.join([str(i) for i in sousRegles]) + self.subRegJoin[1] + self.inCo + str(etatVoisin) + self.andCo
				reglesExt = reglesExt[0:-len(self.andCo)]
			reglesExt += self.regSep
		return reglesExt[0:-len(self.regSep)]

	# split qui ne conserve pas les chaines vides
	def _splitNE(self, chaine, dlm=None): return [elmt for elmt in chaine.split(dlm) if elmt]

	# vérifie qu'un état (ou un nombre d'états) est valide, du point de vue de la syntaxe comme de sa valeur
	def _etatValide(self, etat, maxNum) :
		etat = etat.strip()
		if not etat.isdigit() : return -1
		etat = int(etat)
		if etat < 0 or etat >= maxNum : return -1
		return etat

	# conversion de la syntaxe utilisateur des règles vers la syntaxe interne (avec vérification des erreurs)
	def extToInt(self, reglesExt, numEtats, voisinage) :
		# regexs pour la vérification de la syntaxe des règles et des sous-règles
		regSyntRegles = """^\d+[\t ]*""" + self.ifCo.strip() + """[\t ]*\\""" + self.subRegJoin[0] + """[^\\""" + self.subRegJoin[0] + """\\""" + self.subRegJoin[1] + """]*\\""" + self.subRegJoin[1] + """[\t ]*""" + self.inCo.strip() + """[\t ]*\d+[\t ]*\
(?:[\t ]*""" + self.andCo.strip() + """[\t ]*\\""" + self.subRegJoin[0] + """[^\\""" + self.subRegJoin[0] + """\\""" + self.subRegJoin[1] + """]*\\""" + self.subRegJoin[1] + """[\t ]*""" + self.inCo.strip() + """[\t ]*\d+[\t ]*)*$"""
		regSyntSousRegles = "\\" + self.subRegJoin[0] + "[\t ]*\d*[\t ]*(?:" + self.orCo.strip() + "[\t ]*\d+[\t ]*)*\\" + self.subRegJoin[1]
		# listes des états de départ, cibles et voisins pour garantir leur unicité et la complétude des règles
		etatsDepartLst = []
		etatsCiblesLst = []
		etatsVoisinsLst = []
		# extraction des données avec vérification de la syntaxe et des valeurs
		regles = {}
		for condExt in self._splitNE(reglesExt.strip(), self.fromCo.strip()) :
			condExtElmts = self._splitNE(condExt.strip(), self.toCo.strip())
			etatDepart = self._etatValide(condExtElmts[0], numEtats)
			if etatDepart == -1 or etatDepart in etatsDepartLst : return False
			regles[etatDepart] = {} # par ex : from 0 => {0: {}}
			etatsDepartLst.append(etatDepart)
			maxNumVoisins = pow((voisinage[etatDepart] + 1) * 2 - 1, 2) + 1
			for condExtElmt in condExtElmts[1:] :
				if not re.match(regSyntRegles, condExtElmt.strip()) : return False
				reglesExtElmts = self._splitNE(condExtElmt.strip(), self.ifCo.strip())
				etatCible = self._etatValide(reglesExtElmts[0], numEtats)
				if etatCible == -1 or etatCible in etatsCiblesLst : return False
				regles[etatDepart][etatCible] = {} # par ex : to 1 => {0: {1 : {}}
				etatsCiblesLst.append(etatCible)
				regleExt = self._splitNE(reglesExtElmts[1].strip(), self.andCo.strip())
				for sousReglesExt in regleExt :
					sousReglesExtElmts = self._splitNE(sousReglesExt.strip(), self.inCo.strip())
					etatVoisin = self._etatValide(sousReglesExtElmts[1], numEtats)
					if etatVoisin == -1 or etatVoisin == 0 or etatVoisin in etatsVoisinsLst : return False
					regles[etatDepart][etatCible][etatVoisin] = [] # par ex : ...in  1 => {0: {1 : {1 : []}}
					etatsVoisinsLst.append(etatVoisin)
					if not re.match(regSyntSousRegles, sousReglesExtElmts[0].strip()) : return False
					sousReglesLstExt = self._splitNE(sousReglesExtElmts[0].strip().strip(self.subRegJoin).strip(), self.orCo.strip())
					for sousRegleExt in sousReglesLstExt :
						sousRegle = self._etatValide(sousRegleExt, maxNumVoisins)
						if sousRegle == -1 : return False
						regles[etatDepart][etatCible][etatVoisin].append(sousRegle) # par ex : if (0 or 1 or 2)... {0: {1 : {1 : [0, 1, 2]}}
				if len(etatsVoisinsLst) != (numEtats - 1) : return False
				etatsVoisinsLst = []
			if len(etatsCiblesLst) != (numEtats - 1) : return False
			etatsCiblesLst = []
		if len(etatsDepartLst) != numEtats : return False
		return regles

